package edu.bbte;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Random;


/**
 * @author      Zalan Czirjak <zaorsz@yahoo.com>
 * @version     2011.0603
 * @since       1.6
 */

public class CubeBuilder {

	/**A kocka amit majd felepitunk. ->3dmatrix;*/
	private Matrix3D cube;
	/**A felepitett kocka, amelynek darabjai a vagatok szerint indexelve lesz.*/
	private Matrix3D puz;
	/**A konkavitast garantalo darab forgatasai.*/
	private MatrixCube[] pieces;
	/**A megszerkesztett vagatok.*/
	private MatrixCube[] finalPieces;
	/**A vagatok szama.*/
	private int pieceNum;
	/**Kocka merete.*/
	private int size;
	
	/**
	 * A kocka meretevel inicializalo konstruktor.
	 * @param dim - kocka merete
	 */
	public CubeBuilder(int dim){
		size = dim;
		pieceNum = 2*size;
		cube = new Matrix3D(size);
		cube.init();
		puz = new Matrix3D(size);
		puz.init();
		//megvaltoztatom a kockaba irhato ertekeket ... vagatok megkulonboztetese miatt
		//vagatreszletek elokeszitese
		kPiacePreparation();
		//vegso vagatok elokeszitese
		piacePreparation();
	}
	
	/**
	 * Elokeszit egy vektort a kocka vagatainak tarolasara.
	 */
	private void piacePreparation() {
		finalPieces = new MatrixCube[pieceNum];
		for(int i=0; i<pieceNum; i++){
			finalPieces[i] = new MatrixCube(size);
			finalPieces[i].init();
		}
	}

	/**
	 * A konkavitashoz szukseges darabok forgatasainak elokeszitese (egy tombbe).
	 */
	private void kPiacePreparation(){
		MatrixCube piece = new MatrixCube(size);
		piece.init();
		//konkavitast garantalo darab bealliasa
		piece.setCubeElement(1, 1, 1, 1);
		piece.setCubeElement(1, 1, 2, 1);
		piece.setCubeElement(1, 1, 1, 2);
		//ennek a darabnak az osszes lehetseges forgatasanak legeneralasa
		pieces = new MatrixCube[24];
		pieces = piece.listOfCubeRotations();
	}
	
	/**
	 * Kocka merete*2 db konkav vagatbol felepiti a kockat.
	 * 
	 * Merettol fuggo darab szamu konkav darabokat helyez el az ures kockaban. Minden szintre 2 db ilyen darabot helyez el. 
	 * Majd a le nem foglalt kockakat egy mar behelyezett darabhoz rendeli ugy, hogy a darab konkav maradjon.
	 * 
	 * Van egy olyan eset, hogy eppen az utolso darabot nem tudjuk behelyezni sehogysem az utolso szintre, 
	 * erre a celre van beepitve egy timeout. Ha ez a timeout lejar, akkor erdemesebb ujrakezdeni az egesz 
	 * generalast mintsem egy helyet keresni az utolso darabnak a kockaban.
	 */
	public void build(){
		Random gen = new Random();
		boolean restart=false, end=false;
		do{
			cube.init();
			puz.init();
		//kivalasztok egy konkav vagatot es az megprobalom beilleszteni a kockaba
			//System.out.println("Elkezdtem a rakosgatast.");
			long stime=System.currentTimeMillis();
			out:
			for(int num = 0; num < pieceNum; num++){
				while(!attempt(num,pieces[gen.nextInt(24)],gen))
				{ long time;
					if ((time=System.currentTimeMillis()-stime)>(0.1/1000F))
						{//System.out.println("Elegem van."); 
						restart=true; break out;}
				}
			
		}
		//System.out.println("Rakosgatast befejeztem.");
		if(!restart){
			if(fill())
				end=true;
		}else restart=false;
		//a marad hezagok "betomese" ... valamilyen vagathoz valo csatolasa
		}while(!end); //addig ameddig sikerul kitolteni
	}
	/**
	 * A parameterben megjeleno kockareszt megprobaljuk beilleszteni a kocka bizonyos szintjere.
	 * @param id	- hanyadik db. kockareszlet amit beillesztunk
	 * @param part	- maga a kockareszlet
	 * @param gen	- egy random generator ... kesobb kell
	 * @return	true, ha a beillesztes sikeres volt, kulonben false.
	 */
	private boolean attempt(int id, MatrixCube part, Random gen){
		//magassag beallitasa
		//a darabot felteszitjuk a tetejeb
		
		//System.out.println("Kivalasztott darab:");
		//part.getCube().printMatrix();
		//System.out.println("------");
		//System.out.println("Feltolas");
		while(part.shiftCubeUp2()){
			//System.out.println("------");
			}
		//System.out.println("------");
		
		//annyit nyomjuk le, hogy minden szinten 2 darabka legyen
		int num=(int)id/2;
		
		//System.out.println("Letolas "+num+" szintet");
		while(num!=0&&part.shiftCubeDown2()){
			//System.out.println("------");
			num--;
			}
		//System.out.println("------");
		
		//elmegyunk teljesen balra
		//System.out.println("Balra megyunk...");
		while(part.shiftCubeLeft2()){
			//System.out.println("------");
			}
		//System.out.println("------");	
		//bejarjuk az adott szintet
			return walk(part,id);
		//}
	}
	/**
	 * Bejar egy szintet a kockabol, helyet keresve az adott kockareszenek.
	 * @param part - kockaresz
	 * @return	true, ha kapott egy helyet (akkor be is illeszti), kulonben false.
	 */
	private boolean walk(MatrixCube part, int id){
		//teljesen a kocka vegebe rakjuk
		
		//System.out.println("Elore megyunk...");
		while(part.shiftCubeForward2()){
			//System.out.println("------");
			}
		//System.out.println("------");
		
		boolean back=true;
		//majd onnantol kezdve ameddig meg lehet jobbra, hol elore menve,
		//hol hatra menve megnezzuk a lehetosegeinket.
		do{
			while(back){
				if(cube.canAddMatrix(part.getCube()))
					return modSum(part,id);
				else
					if(!part.shiftCubeBack2())
						back=false;
			}
			while(!back){
				if(cube.canAddMatrix(part.getCube()))
					return modSum(part,id);
				else
					if(!part.shiftCubeForward2())
						back=true;
			}
			
		}while(part.shiftCubeRight2());
		return false;
	}
		
	public Matrix3D getPuz() {
		return puz;
	}

	public Matrix3D getCube() {
		return cube;
	}
	
	/**
	 * Behelyezi a kockaba a parameterkent megadott darabot, a megadott id-val.
	 * @param part - a vagat amit behelyezunk a kockaba.
	 * @param id - amilyen indexxel vonatkozunk majd ra a puzzelben.
	 * @return true ha sikerult behelyezni, kulonben false.
	 */
	private boolean modSum(MatrixCube part, int id){
		//System.out.println("------modSum------");
		//System.out.println("Cube");
		//cube.printMatrix();
		//System.out.println("Part");
		//part.getCube().printMatrix();
		//System.out.println("----------");
		for(int i=0; i<part.getSize(); i++)
			for(int j=0; j<part.getSize(); j++)
				for(int k=0; k<part.getSize(); k++)
					if(1==part.getCube().getElement(i, j, k)){
						cube.setElement(1, i, j, k);
						puz.setElement(id+2, i, j, k);
						//puz.setCubeElement(id+2, i, j, k);
					}
		//System.out.println("------");
		//System.out.println("Puz");
		//puz.printMatrix();
		//System.out.println("Cube");
		//cube.printMatrix();
		//System.out.println("-----------------");
		return true;
	}
	/**
	 * A konkav darabkak behelyezese utan az ures reszeket megprobalja kitolteni.
	 * 
	 * A konkav reszeckek behelyezese utan nem minden kocka tartozik egy vagathoz. 
	 * Ez az eljara ezeket az "ures kockakat" hozzacsatolja egy vagathoz es a vagat id-jat
	 * adja neki. Roviden adott vagatot kiterjeszt egy adott ures reszre, ameddig nem marad
	 * ures resz, de a vagatok konkavok maradnak.
	 * 
	 * @return	true, ha a kitoltes sikeres volt, kulonben false.
	 */
	private boolean fill(){
		int step=puz.emptySpace();
		out:
		do{
			for (int i = 0; i < size; ++i)
				for (int j = 0; j < size; ++j)
					for (int k = 0; k < size; ++k)
						if (0==puz.getElement(i,j,k)){
							puz.setElement(neighbour(i,j,k), i, j, k);
							//System.out.println("------");
							//puz.printMatrix();
							if((0==puz.getElement(i, j, k))&&(1==step))
								{//System.out.println("Visszamentem ...");
								return false;}
							if (puz.getElement(i, j, k)!=0){
								step--;
								if (0==step)
									break out;
							}
						}
		}while(step!=0);
		return true;
	}
	/**
	 * Adott pozicioju "ures kocka" egy szomszedos vagat id-jat teriti vissza.
	 * 
	 * Potencialisan annak a vagatnak az id-jat, amihez kesobb csatoljuk ugy, hogy maga a vagat konkav maradjon.
	 * A kivalasztott meg le nem foglalt kicsi kockahoz veletlenszeruen kivalaszt egy mar letezo szomszedos vagatot
	 * es ellenorzi, hogy a vagat kiegeszitese a kicsi kockaval konkav marad-e.
	 * @param i - "ures kocka" x koordinataja
	 * @param j - "ures kocka" y koordinataja
	 * @param k - "ures kocka" z koordinataja
	 * @return	szomszedos vagat id-ja (int).
	 */
	private int neighbour(int i, int j, int k){	
		Random gen= new Random();
		
			int luckyNum = gen.nextInt(6);
			
			switch(luckyNum){
			case 0:
				if(((i+1)>=0)&&((i+1)<puz.getSize()))
					if(puz.getElement(i+1,j,k)!=0)
						if(isConcave(i,j,k,puz.getElement(i+1,j,k)))
							return puz.getElement(i+1,j,k);
				break;
			case 1:
				if(((j+1)>=0)&&((j+1)<puz.getSize()))
					if(puz.getElement(i,j+1,k)!=0)
						if(isConcave(i,j,k,puz.getElement(i,j+1,k)))
							return puz.getElement(i,j+1,k);
				break;
			case 2:
				if(((k+1)>=0)&&((k+1)<puz.getSize()))
					if(puz.getElement(i,j,k+1)!=0)
						if(isConcave(i,j,k,puz.getElement(i,j,k+1)))
							return puz.getElement(i,j,k+1);
				break;
			case 3:
				if(((i-1)>=0)&&((i-1)<puz.getSize()))
					if(puz.getElement(i-1,j,k)!=0)
						if(isConcave(i,j,k,puz.getElement(i-1,j,k)))
							return puz.getElement(i-1,j,k);
				break;
			case 4:
				if(((j-1)>=0)&&((j-1)<puz.getSize()))
					if(puz.getElement(i,j-1,k)!=0)
						if(isConcave(i,j,k,puz.getElement(i,j-1,k)))
							return puz.getElement(i,j-1,k);
				break;
			case 5:
				if(((k-1)>=0)&&((k-1)<puz.getSize()))
					if(puz.getElement(i,j,k-1)!=0)
						if(isConcave(i,j,k,puz.getElement(i,j,k-1)))
							return puz.getElement(i,j,k-1);
				break;
			}
			return 0;
	}
	
	/**
	 * Ellenorzi, hogy egy adott vagat egy kicsi kockaval valo kiegeszitese konkav vagatot add-e.
	 * 
	 * A kicsi kocka vagathoz valo hozzailesztese akkor eredmenyez egy konkav vagatot, 
	 * ha letezik a vagatban egy olyan kicsi kocka, amelynek ket szomszedja hozzatartozik 
	 * a vagathoz, de ezeknek a szomszedoknak csak egy kozos szomszedjuk tartozik a vagathoz.
	 * @param i - "ures kocka" x koordinataja
	 * @param j - "ures kocka" y koordinataja
	 * @param k - "ures kocka" z koordinataja
	 * @param id - a vagat indexe
	 * @return true, ha a vagat konkav marad, kulonben false.
	 */
	private boolean isConcave(int i, int j, int k, int id){
	
		if(0==id) 
			return false;
		
		Matrix3D temp= new Matrix3D(size);
		temp.setMatrix(puz.getMatrix());
		//temp.printMatrix();
		temp.setElement(id, i, j, k);
		//System.out.println("---Test---");
		//System.out.println("id:"+id+", i:"+i+", j:"+j+", k:"+k+" => "+temp.getElement(i, j, k));
		//temp.printMatrix();
		//System.out.println("--------");
		
		for (int ii = 0; ii < size; ++ii)
			for (int jj = 0; jj < size; ++jj)
				for (int kk = 0; kk < size; ++kk)
					if(id==temp.getElement(ii, jj, kk)) {
						if(((ii-1)>0)&&((ii-1)<size)&&((jj-1)>0)&&((jj-1)<size))
							if((temp.getElement(ii-1, jj-1, kk)!=id)&&(temp.getElement(ii-1, jj, kk)==id)&&(temp.getElement(ii, jj-1, kk)==id))
								return true;
						
						if(((ii-1)>0)&&((ii-1)<size)&&((kk-1)>0)&&((kk-1)<size))
							if((temp.getElement(ii-1, jj, kk-1)!=id)&&(temp.getElement(ii-1, jj, kk)==id)&&(temp.getElement(ii, jj, kk-1)==id))
								return true;
						
						if(((kk-1)>0)&&((kk-1)<size)&&((jj-1)>0)&&((jj-1)<size))
							if((temp.getElement(ii, jj-1, kk-1)!=id)&&(temp.getElement(ii, jj, kk-1)==id)&&(temp.getElement(ii, jj-1, kk)==id))
								return true;
						
						if(((ii+1)>0)&&((ii+1)<size)&&((jj+1)>0)&&((jj+1)<size))
							if((temp.getElement(ii+1, jj+1, kk)!=id)&&(temp.getElement(ii+1, jj, kk)==id)&&(temp.getElement(ii, jj+1, kk)==id))
								return true;
						
						if(((ii+1)>0)&&((ii+1)<size)&&((kk+1)>0)&&((kk+1)<size))
							if((temp.getElement(ii+1, jj, kk+1)!=id)&&(temp.getElement(ii+1, jj, kk)==id)&&(temp.getElement(ii, jj, kk+1)==id))
								return true;
						
						if(((kk+1)>0)&&((kk+1)<size)&&((jj+1)>0)&&((jj+1)<size))
							if((temp.getElement(ii, jj+1, kk+1)!=id)&&(temp.getElement(ii, jj, kk+1)==id)&&(temp.getElement(ii, jj+1, kk)==id))
								return true;
						
						if(((ii-1)>0)&&((ii-1)<size)&&((jj+1)>0)&&((jj+1)<size))
							if((temp.getElement(ii-1, jj+1, kk)!=id)&&(temp.getElement(ii-1, jj, kk)==id)&&(temp.getElement(ii, jj+1, kk)==id))
								return true;
						
						if(((ii-1)>0)&&((ii-1)<size)&&((kk+1)>0)&&((kk+1)<size))
							if((temp.getElement(ii-1, jj, kk+1)!=id)&&(temp.getElement(ii-1, jj, kk)==id)&&(temp.getElement(ii, jj, kk+1)==id))
								return true;
						
						if(((kk-1)>0)&&((kk-1)<size)&&((jj+1)>0)&&((jj+1)<size))
							if((temp.getElement(ii, jj+1, kk-1)!=id)&&(temp.getElement(ii, jj, kk-1)==id)&&(temp.getElement(ii, jj+1, kk)==id))
								return true;
						
						if(((ii+1)>0)&&((ii+1)<size)&&((jj-1)>0)&&((jj-1)<size))
							if((temp.getElement(ii+1, jj-1, kk)!=id)&&(temp.getElement(ii+1, jj, kk)==id)&&(temp.getElement(ii, jj-1, kk)==id))
								return true;
						
						if(((ii+1)>0)&&((ii+1)<size)&&((kk-1)>0)&&((kk-1)<size))
							if((temp.getElement(ii+1, jj, kk-1)!=id)&&(temp.getElement(ii+1, jj, kk)==id)&&(temp.getElement(ii, jj, kk-1)==id))
								return true;
						
						if(((kk+1)>0)&&((kk+1)<size)&&((jj-1)>0)&&((jj-1)<size))
							if((temp.getElement(ii, jj-1, kk+1)!=id)&&(temp.getElement(ii, jj, kk+1)==id)&&(temp.getElement(ii, jj-1, kk)==id))
								return true;
					}
					
		return false;
	}
	
	/**
	 * Kivalogatja es kulon valasztja a vagatokat a kirakott kockabol.
	 * 
	 * Mivel a vagatokat eltolja es forgatja is, igy gyakorlatilag egy jatekot general ki.
	 */
	public void separate(){
		for(int num=0; num<pieceNum; num++){
			
			for (int i = 0; i < size; ++i)
				for (int j = 0; j < size; ++j)
					for (int k = 0; k < size; ++k)
						if(puz.getElement(i, j, k)==(num+2))
							finalPieces[num].setCubeElement(1, i, j, k);
		}
		spin();
	}
	
	/**
	 * A vagatokat a kozeppont fele tolja es meg is forgatja.
	 */
	private void spin(){
		for(int num=0; num<pieceNum; num++){
			while(finalPieces[num].shiftCubeUp2()){}
			while(finalPieces[num].shiftCubeLeft2()){}
			while(finalPieces[num].shiftCubeBack2()){}

			for(int i=0; i<size-2; i++)
				finalPieces[num].shiftCubeForward2();
			for(int i=0; i<size-2; i++)
				finalPieces[num].shiftCubeDown2();
			for(int i=0; i<size-2; i++)
				finalPieces[num].shiftCubeRight2();
			
			Random gen = new Random();
			int rot=gen.nextInt(3);
			for(int i=0; i<rot; i++){
				finalPieces[num].rotateCubeX();
				finalPieces[num].rotateCubeY();
				finalPieces[num].rotateCubeZ();
			}
		}
	}	

	/**
	 * Visszateriti a felepitett kocka-kirakos darabjainak matrixat.
	 * @return MatrixCube tipusu tomb, amely tartalmazza a kirakost felepito vagatokat.
	 */
	public MatrixCube [] getFinalPieces() {
		return finalPieces;
	}

	/**
	 * Visszateriti a generalt vagatok szamat.
	 * @return a kirakost felepito vagatok szama.
	 */
	public int getPieceNum() {
		return pieceNum;
	}
	/**
	 * Fileba kiirja a megszerkesztett jatekot.
	 * 
	 *  Elso sorban a kocka merete van.
	 *  Utana 3D-s matrixok egy 2 db ures sorral elvalasztva.
	 * @param fileName - file teljes neve amibe menteni akarjuk a jatekot.
	 * @throws IOException
	 */
	public void printGame(String fileName) throws IOException{
		BufferedWriter out = new BufferedWriter(new FileWriter(fileName));
		//elso sor kocka merete + ures sor
		out.write(Integer.toString(size)); out.newLine(); out.newLine();	
		//utanna ures sorokkal elvalasztva a 3D-s matrixok
			for(int num=0; num<pieceNum; num++){
				
				for(int k=0; k<size; k++){
					for(int i=0; i<size; i++){
						for(int j=0; j<size; j++){
							out.write(Integer.toString(finalPieces[num].getCubeElement(i,j,k))+" ");
							//System.out.print(Integer.toString(finalPieces[num].getCubeElement(i,j,k))+" ");
						}
						out.newLine();
						//System.out.println();
					}
					out.newLine();
					//System.out.println();
				}
				out.newLine();
				//System.out.println();
			}
			out.close();
	}
	
	public static void main (String[] args){
		long stime=System.currentTimeMillis();
		CubeBuilder cube = new CubeBuilder(4);
		cube.build();
		long time=System.currentTimeMillis()-stime;
		System.out.println("Build time:"+(time/1000F)+" sec");
		//cube.getPuz().printMatrix();
		cube.separate();
		System.out.println("Game edit time:"+(time/1000F)+" sec");
		System.out.println("------------");
		for(int num=0; num < cube.getPieceNum(); num++){
			cube.getFinalPieces()[num].printCube();
		}
		/*
		System.out.println("Jatek kimentese.");
		
		try{
		cube.printGame("Jatek.txt");
		}
		catch(IOException exp){
			System.err.println("Nem tudtam kimenteni a jatekot!");
			exp.pr01intStackTrace();
		}
		*/
	}
}


